using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace Jeffrey.XFramework.XCrawl
{
    public static class CrawlToolKits
    {
        [DllImport("wininet")]
        private static extern Int32 InternetGetConnectedState(ref Int32 lpdwFlags, Int32 dwReserved);

        // Performance Counter to measure memory usage
        private static PerformanceCounter ramCounter = new PerformanceCounter("Memory", "Available MBytes");
        // Performance Counter to measure CPU usage
        private static PerformanceCounter cpuCounter = new PerformanceCounter();

        static CrawlToolKits()
        {
            cpuCounter.CategoryName = "Processor";
            cpuCounter.CounterName  = "% Processor Time";
            cpuCounter.InstanceName = "_Total";
        }

        /*
         * Default file patterns are:
         * Images: .bmp, .gif, .jp(e)g, .png, .tif(f)
         * Audio:  .mid, .mp2, .mp3, .mp4, .wav
         * Video:  .avi, .mov, .mpeg, .ram .rm .smil, .wmv
         * Miscellaneous: .doc, .pdf, .ppt, .swf, .exe, .rar, .js, .gz, .zip, .css, .xml, .xsd, .dtd, .cab
         */
        public static String FilePatterns
        {
            get
            {
                return @".+\." +
                       @"(s?html?|aspx?|jsp?|php|" +
                       @"bmp|gif|jpe?g|png|tiff?|" +
                       @"mid|mp(2|3|4)|wav|avi|mov|mpeg|ram|rm|smil|wmv|mkv|" +
                       @"docx?|pdf|pptx?|swf|" +
                       @"exe|rar|gz|zip|css|xml|xsd|dtd|cab" +
                       @")$";
            }
        }

        public static String GetAbsolutePath(String absoluteDir, String relativeFile)
        {
            if (relativeFile == null || relativeFile == String.Empty)
                return null;

            Boolean isNotOver = true;
            Int32 intStart = 0;

            while (isNotOver)
            {
                if (relativeFile.StartsWith(@"..\"))
                {
                    relativeFile = relativeFile.Remove(0, 3);
                    intStart++;
                }
                else
                    isNotOver = false;
            }
            if (intStart > 0)
            {
                if (absoluteDir.EndsWith("\\"))
                    absoluteDir = absoluteDir.Remove(absoluteDir.Length - 1);

                for (Int32 i = 0; i < intStart; i++)
                    absoluteDir = absoluteDir.Remove(absoluteDir.LastIndexOf("\\"));
            }

            return Path.Combine(absoluteDir, relativeFile);
        }

        public static UInt32 StringHash(String uriString)
        {
            UInt32 hash = 0;
            UInt32 x = 0;

            for (Int32 i = 0; i < uriString.Length; i++)
            {
                hash = (hash << 4) + uriString[i];
                if ((x = hash & 0xFF000000U) != 0)
                {
                    hash ^= (x >> 24);
                    hash &= ~x;
                }
            }
            return (hash & 0x7FFFFFFF);
        }

        public static String HttpStatusCodesToString(Int32 code)
        {
            switch (code)
            {
                // DNS
                //case S_DNS_SUCCESS: return "DNS-1-OK";
                // HTTP Informational 1xx
                case 100: return "HTTP-100-Info-Continue";
                case 101: return "HTTP-101-Info-Switching Protocols";
                // HTTP Successful 2xx
                case 200: return "HTTP-200-Success-OK";
                case 201: return "HTTP-201-Success-Created";
                case 202: return "HTTP-202-Success-Accepted";
                case 203: return "HTTP-203-Success-Non-Authoritative";
                case 204: return "HTTP-204-Success-No Content ";
                case 205: return "HTTP-205-Success-Reset Content";
                case 206: return "HTTP-206-Success-Partial Content";
                // HTTP Redirection 3xx
                case 300: return "HTTP-300-Redirect-Multiple Choices";
                case 301: return "HTTP-301-Redirect-Moved Permanently";
                case 302: return "HTTP-302-Redirect-Found";
                case 303: return "HTTP-303-Redirect-See Other";
                case 304: return "HTTP-304-Redirect-Not Modified";
                case 305: return "HTTP-305-Redirect-Use Proxy";
                case 307: return "HTTP-307-Temporary Redirect";
                // HTTP Client Error 4xx
                case 400: return "HTTP-400-ClientErr-Bad Request";
                case 401: return "HTTP-401-ClientErr-Unauthorized";
                case 402: return "HTTP-402-ClientErr-Payment Required";
                case 403: return "HTTP-403-ClientErr-Forbidden";
                case 404: return "HTTP-404-ClientErr-Not Found";
                case 405: return "HTTP-405-ClientErr-Method Not Allowed";
                case 407: return "HTTP-406-ClientErr-Not Acceptable";
                case 408: return "HTTP-407-ClientErr-Proxy Authentication Required";
                case 409: return "HTTP-408-ClientErr-Request Timeout";
                case 410: return "HTTP-409-ClientErr-Conflict";
                case 406: return "HTTP-410-ClientErr-Gone";
                case 411: return "HTTP-411-ClientErr-Length Required";
                case 412: return "HTTP-412-ClientErr-Precondition Failed";
                case 413: return "HTTP-413-ClientErr-Request Entity Too Large";
                case 414: return "HTTP-414-ClientErr-Request-URI Too Long";
                case 415: return "HTTP-415-ClientErr-Unsupported Media Type";
                case 416: return "HTTP-416-ClientErr-Requested Range Not Satisfiable";
                case 417: return "HTTP-417-ClientErr-Expectation Failed";
                // HTTP Server Error 5xx
                case 500: return "HTTP-500-ServerErr-Internal Server Error";
                case 501: return "HTTP-501-ServerErr-Not Implemented";
                case 502: return "HTTP-502-ServerErr-Bad Gateway";
                case 503: return "HTTP-503-ServerErr-Service Unavailable";
                case 504: return "HTTP-504-ServerErr-Gateway Timeout";
                case 505: return "HTTP-505-ServerErr-HTTP Version Not Supported";
                //Default
                default: return code.ToString();
            }
        }

        public static String UriStatusToString(CrawlUri.StatusCode code)
        {
            switch (code)
            {
                //S_xxx succeed...
                case CrawlUri.StatusCode.S_Succeed: return "The uri is completely processed";
                //N_xxx normal conditions...
                case CrawlUri.StatusCode.N_RejectedByAll: return "The uri is rejected by all the processors";
                //E_xxx errors...
                case CrawlUri.StatusCode.E_CannotDeterminePage: return "The uri should be redirected to another location, but can not determine the page";
                case CrawlUri.StatusCode.E_Connected: return "Can not connect the host";
                case CrawlUri.StatusCode.E_CustomizedError: return "The error is defined by user";
                case CrawlUri.StatusCode.E_Extract: return "Error occurred in Extract-Chain";
                case CrawlUri.StatusCode.E_Fetch: return "Error occurred in Fetch-Chain";
                case CrawlUri.StatusCode.E_Filter: return "Error occurred in Filters";
                case CrawlUri.StatusCode.E_HeaderParse: return "Can not parse the response header";
                case CrawlUri.StatusCode.E_HeaderReceive: return "Can not receive the response header";
                case CrawlUri.StatusCode.E_HostNotFound: return "The host not found";
                case CrawlUri.StatusCode.E_NeedRetry: return "The uri will be reprocessed later";
                case CrawlUri.StatusCode.E_PostFetch: return "Error occurred in PostFetch-Chain";
                case CrawlUri.StatusCode.E_PreFetch: return "Error occurred in PreFetch-Chain";
                case CrawlUri.StatusCode.E_ReceiveChunkedData: return "Can not receive the chunked data from the server";
                case CrawlUri.StatusCode.E_ReceiveData: return "Can not receive data from the server";
                case CrawlUri.StatusCode.E_RequestSend: return "Error occurred when sending request to host";
                case CrawlUri.StatusCode.E_ResolveHost: return "Can not resolve the host";
                case CrawlUri.StatusCode.E_ResponsedCodeParse: return "Can not parse the http-responsed code";
                case CrawlUri.StatusCode.E_ResponsedErrorCode: return "Server responsed error code";
                case CrawlUri.StatusCode.E_RetryFailed: return "The uri reprocessed failed";
                case CrawlUri.StatusCode.E_Sent: return "Error occurred when sending data to server";
                case CrawlUri.StatusCode.E_SetTimeOut: return "Error occurred when setting the socket time-out values";
                case CrawlUri.StatusCode.E_Storage: return "Error occurred in Storage-Chain";
                case CrawlUri.StatusCode.E_UriNotFormatted: return "the uri may not well formatted";
                default: return code.ToString();
            }
        }

        /*
         * according to the page: http://en.wikipedia.org/wiki/URL_normalization/
         * implemented some parts of the url-normalization rules...
         */
        public static String UrlNormalization(String absolute_uri)
        {
            if (absolute_uri == null)
                throw new ArgumentNullException("absolute_uri can not be null!"); ;

            CrawlUri uri;
            try
            {
                uri = new CrawlUri(absolute_uri);
            }
            catch
            {
                return String.Empty;
            }

            //trim the space of the uri
            absolute_uri = absolute_uri.Trim();
            //Converting the entire URL to lower case.
            absolute_uri = absolute_uri.ToLower();

            //Default directory indexes are generally not needed in URLs.
            Int32 index = absolute_uri.IndexOf("/default.");
            if (index != -1)
                absolute_uri = absolute_uri.Remove(index + 1);
            else
            {
                index = absolute_uri.IndexOf("/index.");
                if (index != -1)
                    absolute_uri = absolute_uri.Remove(index + 1);
            }

            //The fragment('#') component of a URL is usually removed.
            index = absolute_uri.IndexOf('#');
            if (index != -1) 
                absolute_uri = absolute_uri.Remove(index);

            if (!absolute_uri.EndsWith("/"))
            {
                /*
                 * in case the url like:
                 * "http://xxx.xxxx.xxx"
                 */
                if (uri.AbsolutePath == "/")
                    absolute_uri += '/';
                else
                {
                    //Adding trailing / Directories are indicated with a trailing slash and should be included in URLs.
                    Match match = new Regex(FilePatterns, RegexOptions.IgnoreCase).Match(uri.AbsolutePath);
                    /*
                     * if not match the pattern and also not end wish a slash
                     * we can assume that it is directory...add slash to the end...
                     */
                    if (!match.Success)
                        absolute_uri += '/';
                }
            }

            return absolute_uri;
        }

        public static String UrlNormalization(Uri uri)
        {
            if (uri == null)
                throw new ArgumentNullException("uri can not be null!"); ;

            String absolute_uri = uri.AbsoluteUri;

            //trim the space of the uri
            absolute_uri = absolute_uri.Trim();
            //Converting the entire URL to lower case.
            absolute_uri = absolute_uri.ToLower();

            //Default directory indexes are generally not needed in URLs.
            Int32 index = absolute_uri.IndexOf("/default.");
            if (index != -1)
                absolute_uri = absolute_uri.Remove(index + 1);
            else
            {
                index = absolute_uri.IndexOf("/index.");
                if (index != -1)
                    absolute_uri = absolute_uri.Remove(index + 1);
            }

            //The fragment('#') component of a URL is usually removed.
            index = absolute_uri.IndexOf('#');
            if (index != -1)
                absolute_uri = absolute_uri.Remove(index);

            if (!absolute_uri.EndsWith("/"))
            {
                /*
                 * in case the url like:
                 * "http://xxx.xxxx.xxx"
                 */
                if (uri.AbsolutePath == "/")
                    absolute_uri += '/';
                else
                {
                    //Adding trailing / Directories are indicated with a trailing slash and should be included in URLs.
                    Match match = new Regex(FilePatterns, RegexOptions.IgnoreCase).Match(uri.AbsolutePath);
                    /*
                     * if not match the pattern and also not end wish a slash
                     * we can assume that it is directory...add slash to the end...
                     */
                    if (!match.Success)
                        absolute_uri += '/';
                }
            }

            return absolute_uri;
        }

        public static String Commas(Int32 nNum)
        {
            String str = nNum.ToString();
            Int32 nIndex = str.Length;

            while (nIndex > 3)
            {
                str = str.Insert(nIndex - 3, ",");
                nIndex -= 3;
            }
            return str;
        }

        public static Boolean LANConnectedState(out String description)
        {
            Int32 nState = 0;

            if (InternetGetConnectedState(ref nState, 0) == 0)
            {
                description = "Disconnected";
                return false;
            }
            else
            {
                if ((nState & 1) == 1)
                    description = "Modem connection";
                else if ((nState & 2) == 2)
                    description = "LAN connection";
                else if ((nState & 4) == 4)
                    description = "Proxy connection";
                else if ((nState & 8) == 8)
                    description = "Modem is busy with a non-Internet connection";
                else if ((nState & 0x10) == 0x10)
                    description = "Remote Access Server is installed";
                else if ((nState & 0x20) == 0x20)
                    description = "Offline";
                else if ((nState & 0x40) == 0x40)
                    description = "Internet connection is currently configured";
                else
                    description = "Status unknown";
                return true;
            }
        }

        public static Int32 CpuUsage
        {
            get { return (Int32)cpuCounter.NextValue(); }
        }

        public static Int32 FreeMemorySize
        {
            get { return (Int32)ramCounter.NextValue(); }
        }
    }
}